/*
 * Copyright 2016 小新de蜡笔
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "list.h"
#include "xml_bean.h"

typedef struct xml_node {
	int type;
	char *key; // 标签
	char *val; // 标签内的值
	struct list_head child; // 子节点链表头
	struct list_head list; // 同级节点链表
} xnode_st;

int xb_new_xobj (XOBJ **element, const char *key, const char *val)
{
	int err = 0;
	xnode_st *pnode = NULL;
	ssize_t keylen = 0;
	ssize_t vallen = 0;

	if ((NULL == element) \
		|| NULL == key \
		|| !strcmp("", key)) {
		err = -EINVAL;
		goto e_check_arguments;
	}

	pnode = malloc(sizeof(*pnode));
	if (!pnode) {
		err = -ENOMEM;
		goto e_malloc_pnode;
	}

	keylen = strlen(key) + 1;
	pnode->key = malloc(keylen);
	if (!pnode->key) {
		err = -ENOMEM;
		goto e_malloc_key;
	}
	memset(pnode->key, 0, keylen);
	strncpy(pnode->key, key, keylen);

	pnode->val = NULL;
	if (NULL != val) {
		vallen = strlen(val) + 1;
		pnode->val = malloc(vallen);
		if (!pnode->val) {
			err = -ENOMEM;
			goto e_malloc_val;
		}
		memset(pnode->val, 0, vallen);
		strncpy(pnode->val, val, vallen);
	}

	INIT_LIST_HEAD(&pnode->child);
	INIT_LIST_HEAD(&pnode->list);

	*element = pnode;

	return 0;

	free(pnode->val);
e_malloc_val:
	free(pnode->key);
e_malloc_key:
	free(pnode);
e_malloc_pnode:
e_check_arguments:
	return err;
}

void xb_dealloc_xobj (XOBJ *element)
{
	xnode_st *pnode = element;
	if (NULL != pnode) {
		free(pnode->key);
		free(pnode->val);
		free(pnode);
	}
}

int xb_add_content (XOBJ *parent_element, XOBJ *sub_element)
{
	int err = 0;
	xnode_st *ppnode = NULL;
	xnode_st *pcnode = NULL;

	if ((NULL == parent_element) \
		|| (NULL == sub_element)) {
		err = -EINVAL;
		goto e_check_arguments;
	}

	ppnode = parent_element;
	pcnode = sub_element;

	list_add_tail(&pcnode->list, &ppnode->child);

	return 0;

e_check_arguments:
	return err;
}

int _xb_gen_xml_child (xnode_st *pnode, xnode_st *parent, char **buf, size_t *bufsize, int level)
{
	int err = 0;
	int i = 0, j = 0;
	char *skey = NULL;
	char *ekey = NULL;
	char *val = NULL;
	char *tmp_buf = NULL;
	char *start = NULL;
	const char *xml_head = "<?xml version='1.0' encoding='UTF-8' ?>\n";
	size_t skey_len = 0;
	size_t ekey_len = 0;
	size_t node_len = 0;
	size_t write_len = 0; // 长度不包含 '\0'
	size_t const WRAP_LEN = strlen("\n");
	size_t const TAB_LEN = strlen("\t");
	xnode_st *snode = NULL;

	// 关闭缩进
	if (level < 0) {
		level = -2;
	}

	skey_len = strlen(pnode->key) + 2 + 1; // '<' '>' '\0'
	ekey_len = skey_len + 1 + 1; // '<' '/' '>' '\n' '\0'

	skey = malloc(skey_len);
	if (!skey) {
		err = -ENOMEM;
		goto e_malloc_skey;
	}
	snprintf(skey, skey_len, "<%s>", pnode->key);

	if (NULL == pnode->val) {
		node_len = skey_len + ekey_len + WRAP_LEN - 1;
		val = "";
	} else {
		node_len = skey_len + ekey_len + strlen(pnode->val) - 1;
		val = pnode->val;
	}
	j = level;
	if (&pnode->child != pnode->child.next) { // 对于有子节点的节点，在结束标签前也要加 \t。
		j = level * 2;
	}
	// level 为 0 的时候不用加 \t，所以循环条件是 < 而不是 <=。
	for (i = 0; i < j; i++) {
		node_len += TAB_LEN;
	}

	ekey = malloc(ekey_len);
	if (!ekey) {
		err = -ENOMEM;
		goto e_malloc_ekey;
	}
	snprintf(ekey, ekey_len, "</%s>", pnode->key);

	if (NULL == *buf) {
		node_len += strlen(xml_head); // 不需要 + 1，因为 node_len 中已经包含 '\0' 的长度了。
		*buf = malloc(node_len);
		if (!*buf) {
			err = -ENOMEM;
			goto e_alloc_buf;
		}
		*bufsize = node_len;
		start = *buf;

		// 加入 xml 文件头
		write_len += snprintf(start, node_len - write_len, "%s", xml_head) - 1;
		start = *buf + strlen(*buf);
	} else {
		*bufsize = node_len + *bufsize; // node_len 和 bufsize 都包含 \0，所以要 -1。
		tmp_buf = realloc(*buf, *bufsize);
		if (!tmp_buf) {
			err = -ENOMEM;
			goto e_alloc_buf;
		}
		*buf = tmp_buf;
		start = *buf + strlen(*buf);
	}

	for (i = 0; i < level; i++) {
		write_len += snprintf(start, node_len - write_len, "\t") - 1;
		start = *buf + strlen(*buf);
	}
	write_len += snprintf(start, node_len - write_len, "%s", skey) - 1;

	if (NULL == pnode->val) {
		// 如果当前节点的下一个子节点是其本身，说明当前节点没有子节点。
		if (&pnode->child != pnode->child.next) { // 有子节点就去遍历子节点
			// 包含子节点的元素换行显示其子节点
			start = *buf + strlen(*buf);
			write_len += snprintf(start, node_len - write_len, "\n") - 1;

			err = _xb_gen_xml_child(list_entry(pnode->child.next, xnode_st, list) \
					, pnode \
					, buf \
					, bufsize \
					, level + 1);
			if (err) {
				goto e_child;
			}
		}
	}

	// 这个 start 必须在遍历子节点之后计算，否则就把结束标签写到子节点前面了。
	start = *buf + strlen(*buf);
	if (0 != strcmp("", val)) {
		write_len += snprintf(start, node_len - write_len, "%s", val) - 1;
		start = *buf + strlen(*buf);
	}
	if (&pnode->child != pnode->child.next) { // 有子节点时，才需要在结束标签前加 \t。
		for (i = 0; i < level; i++) {
			write_len += snprintf(start, node_len - write_len, "\t") - 1;
			start = *buf + strlen(*buf);
		}
	}
	write_len += snprintf(start, node_len - write_len, "%s\n", ekey) - 1;

	// 如果同级节点是其本身或父节点，说明没有同级节点，否则就去遍历同级节点。
	list_for_each_entry(snode, &pnode->list, list) {
		//snode = list_entry(&pnode->list, xnode_st, list);
		if ((&parent->child == pnode->list.next) \
				|| (pnode->list.next == &pnode->list)) {
			continue;
		} else {
			err = _xb_gen_xml_child(snode, parent, buf, bufsize, level);
			if (err) {
				goto e_same_level;
			}
			break;
		}
	}

	free(ekey);
	free(skey);

	return 0;

e_same_level:
e_child:
e_alloc_buf:
	free(ekey);
e_malloc_ekey:
	free(skey);
e_malloc_skey:
	return err;
}

void _xb_gen_xml_child_free (char *buf)
{
	free(buf);
}

int _xb_gen_xml_list (xnode_st *pnode)
{
	return -1;
}

int xb_gen_xml (XOBJ *element, char **buf, int enable_indent)
{
	xnode_st *pnode = NULL;
	int err = 0;
	char *xml = NULL;
	size_t bufsize = 0;

	/* check arguments */

	pnode = element;
	if ((err = _xb_gen_xml_child(pnode, pnode, &xml, &bufsize, (enable_indent ? 0 : -1)))) {
			_xb_gen_xml_child_free(xml);
			return err;
	}

	*buf = xml;

	return 0;
}

void xb_gen_xml_free (char *buf)
{
	_xb_gen_xml_child_free(buf);
}

/******************** For test ******************/
#if 0
void _create_node(XOBJ **obj, const char *key, const char *val)
{
	if (xb_new_xobj(obj, key, val)) {
		fprintf(stderr, "ERROR: %d: <%s>%s\n", __LINE__, key, val);
		exit(1);
	}

}

int main (void)
{
	int err = 0;
	char *buf = NULL;
	XOBJ *root = NULL;
	XOBJ *photos = NULL;
	XOBJ *name1 = NULL;
	XOBJ *name2 = NULL;
	XOBJ *others = NULL;
	XOBJ *size = NULL;
	XOBJ *infos = NULL;
	XOBJ *path = NULL;
	XOBJ *nothing = NULL;
	XOBJ *videos = NULL;

	_create_node(&root, "root", NULL);
	_create_node(&photos, "photos", NULL);
	_create_node(&name1, "name", "1.jpg");
	_create_node(&name2, "name", "2.jpg");
	_create_node(&others, "others", NULL);
	_create_node(&size, "size", "40");
	_create_node(&infos, "infos", NULL);
	_create_node(&path, "path", "/home/");
	_create_node(&nothing, "nothing", NULL);
	_create_node(&videos, "videos", NULL);

	xb_add_content(root, photos);
	xb_add_content(root, videos);
	xb_add_content(photos, name1);
	xb_add_content(photos, name2);
	xb_add_content(photos, others);
	xb_add_content(others, size);
	xb_add_content(others, infos);
	xb_add_content(infos, path);
	xb_add_content(others, nothing);

	if ((err = xb_gen_xml(root, &buf))) {
		fprintf(stderr, "ERROR: %s\n", strerror(-err));
		return err;
	}
	printf("[1] buf=%p, size=%lld\n", buf, (long long) strlen(buf));
	printf("%s", buf);

	xb_gen_xml_free(buf);
	xb_dealloc_xobj(root);
	xb_dealloc_xobj(photos);
	xb_dealloc_xobj(name1);
	xb_dealloc_xobj(name2);
	xb_dealloc_xobj(others);
	xb_dealloc_xobj(size);
	xb_dealloc_xobj(infos);
	xb_dealloc_xobj(path);
	xb_dealloc_xobj(nothing);
	xb_dealloc_xobj(videos);

	printf("Done.\n");
	
	return 0;
}
#endif

